﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BlackScholesLib
{
    public class BlackScholes_v2
    {
        public static double Calculate(
            double spotPrice,
            double strike,
            double time,
            double rate,
            double dividend,
            double vol,
            string callPut,
            int reg0Fut1 = 0,
            int output = 0)
        {
            // only regular option prices (no greeks)
            if (reg0Fut1 != 0 || output != 0)
                throw new ArgumentException("unsupported");

            var optionType = (callPut == "C") ? "Call" : "Put";
            
            return EuropeanOption(
                CallOrPut:optionType, 
                S:spotPrice,
                K:strike,
                v:vol,
                r:rate,
                T:time,
                q:dividend);
        }

        public static double CalculateImpliedVolatility(
            double spotPrice,
            double strike,
            double time,
            double rate,
            double dividend,
            double volGuess,
            string callPut,
            double optionValue)
        {
            var optionType = (callPut == "C") ? "Call" : "Put";            

            return ImpliedVolatility(
                CallOrPut: optionType,
                S: spotPrice,
                K: strike,
                r: rate,
                T: time,
                q: dividend,
                OptionValue:optionValue,
                guess:volGuess);
        }

        private static double ImpliedVolatility(string CallOrPut, double S, double K, double r, double T, double q, double OptionValue, double guess)
        {
            double dVol = 0.00001;
            double epsilon = 0.00001;
            int maxIter = 100;
            double vol_1 = guess;
            double vol_2;
            int i = 1;
            while(true)
            {
                double Value_1 = EuropeanOption(CallOrPut, S, K, vol_1, r, T, q);
                vol_2 = vol_1 - dVol;
                double Value_2 = EuropeanOption(CallOrPut, S, K, vol_2, r, T, q);
                double dx = (Value_2 - Value_1)/dVol;
                if (Math.Abs(dx) < epsilon || i == maxIter)
                    break;
                vol_1 = vol_1 - (OptionValue - Value_1) / dx;
                i++;
            }
            return vol_1;
        }

        // --------------------------------------------------------
        // from http://investexcel.net/2118/implied-volatility-vba/
        // --------------------------------------------------------
        private static double EuropeanOption(string CallOrPut, double S, double K, double v, double r, double T, double q)
        {
            Func<double, double> NormSDist = NormSDist_v4.NormSDist;
            double d1 = (Math.Log(S / K) + (r - q + 0.5 * Math.Pow(v, 2)) * T) / (v * Math.Sqrt(T));
            double d2 = (Math.Log(S / K) + (r - q - 0.5 * Math.Pow(v, 2)) * T) / (v * Math.Sqrt(T));
            double nd1 = NormSDist(d1);
            double nd2 = NormSDist(d2);
            double nnd1 = NormSDist(-d1);
            double nnd2 = NormSDist(-d2);
            switch (CallOrPut)
            {
                case "Call":
                    return S * Math.Exp(-q * T) * nd1 - K * Math.Exp(-r * T) * nd2;
                case "Put":
                    return -S * Math.Exp(-q * T) * nnd1 - K * Math.Exp(-r * T) * nnd2;
                default:
                    throw new Exception("unhandled");
            }
        }
    }
}
